#!/bin/sh

#
# Generate library dependencies for binaries
#
# Copyright 2024 Odin Kroeger.
#
# This file is part of suCGI.
#
# suCGI is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
#
# suCGI is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
# Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with suCGI. If not, see <https://www.gnu.org/licenses/>.
#

# shellcheck disable=2015

#
# Initialiation
#

set -Cefu
scriptdir="$(cd -P "$(dirname -- "$0")" && pwd)"
: "${scriptdir:?}"
readonly scriptdir
# shellcheck disable=1091
. "$scriptdir/../libutil.sh"	|| exit
init				|| exit
tmpdir				|| exit


#
# Functions
#

# Recursivey print #include-d "files"
gethdrs() (
	_gethdrs_dir="$(dirname "${1:?}")"
	_gethdrs_file="$(basename "${1:?}")"

	cd "$_gethdrs_dir" || return

	sed -n 's/[ \t]*#[ \t]*include[ \t]*"\(.*\)"/\1/p' "$_gethdrs_file" |
	while read -r _gethdrs_hdr
	do
		printf '%s/' "$(cd "$(dirname "$_gethdrs_hdr")" && pwd)"
		basename "$_gethdrs_hdr"
		gethdrs "$_gethdrs_hdr"
	done
)


#
# Options
#

output='' src='' target=''
# shellcheck disable=2154
while getopts 'd:ho:s:t:' opt
do
	case $opt in
	(h) exec cat <<EOF ;;
$progname - Generate makefile dependencies for binaries

Usage:  $progname target library [...]
	$progname -h

Options:
    -d         Enable debugging mode.
    -h         Show this help screen.
    -s file    Read the target's source code from file. 
    -t target  Use target for the dependencies.

Copyright 2024 Odin Kroeger.
Released under the GNU Affero General Public License.
This programme comes with ABSOLUTELY NO WARRANTY.
EOF
	(d) set -x ;;
	(o) output="$OPTARG" ;;
	(s) src="$OPTARG" ;;
	(t) target="$OPTARG" ;;
	(*) exit 2
	esac
done
shift $((OPTIND - 1))

if [ $# -eq 0 ]
then err -s2 "No binary given"
fi

bin="$1"
shift

: "${src:="${bin%.o}.c"}"
: "${target:="$bin"}"


#
# Main
#

atexit="exec 3>&-; ${atexit-}"
if [ "$output" ]
then
	swap="$(dirname "$output")/.$(basename "$output").swp"
	readonly swap
	atexit="
		if [ \$_atexit_retval -eq 0 ]
		then mv \"\$swap\" \"\$output\"
		else rm -f \"\$swap\"
		fi
		${atexit-}
	"
	exec 3>"$swap"
else
	exec 3>&1
fi

printf '%s: \\\n\t%s' "$target" "$src" >&3

# Headers
basedir="$(pwd)/"
gethdrs "$src" |
sort -u	       |
while read -r hdr
do printf ' \\\n\t%s' "${hdr#"$basedir"}" >&3
done

# Libraries
if [ $# -gt 0 ] && [ "${bin%.o}" = "$bin" ]
then
	make "$@" >/dev/null
	nm -AP "$@" >"$TMPDIR/nm.out"

	syms="$(
		cscope -L2 '.*' "$src"		|
		awk '$4 !~ /^#/ {print "_" $2}' |
		sort -u
	)"

	# cscope does not recognise calls made from macros.
	if ! [ "$syms" ]
	then
		syms="$(
			cc -E "$src"	|
			awk '{
				for (i = 0; i < NF; i++) {
					if ($i ~ /^[_[:alnum:]]*(\(|$)/ &&
						($i ~ /\(/ || $(i + 1) ~ /^\(/))
					{
						gsub(/\(.*/, "", $i);
						print "_" $i;
					}
				}
			}'		|
			sort -u
		)"
	fi

	while [ "$syms" ]
	do
		new=
		while IFS=': ' read -r lib name type _
		do
			# shellcheck disable=2086
			if	[ "$type" = T ]		 &&
				inlist '=' "$name" $syms &&
				! inlist '=' "$lib" $new
			then
				new="$new $lib"
			fi
		done <"$TMPDIR/nm.out"

		# shellcheck disable=2086
		if [ "$new" ]
		then printf '%s\n' $new
		fi

		# shellcheck disable=2086
		syms="$(
			awk -F'[: ]' '
			BEGIN {
				for (i = 0; i < ARGC; i++) {
					lib[i] = ARGV[i];
					ARGV[i] = null
				}
			}

			$4 == "U" {
				for (i = 0; i in lib; i++) {
					if ($1 == lib[i]) {
						print $3;
						break;
					}
				}
			}

			' $new <"$TMPDIR/nm.out" |
			sort -u
		)"
	done                  |
	sort -u               |
	sed 's/\[/(/; s/]/)/' |
	xargs printf ' \\\n\t%s' >&3
fi

# Directory sentinel
if srcdir="$(dirname "$src")" && [ "$srcdir" != . ]
then printf ' \\\n\t%s/.sentinel' "$srcdir" >&3
fi

# Separator
printf '\n\n' >&3
